/*
 * Copyright (C) 2013 Manoel Afonso Filho
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package agimagem.util;

import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.awt.image.WritableRaster;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.logging.Level;
import javax.imageio.ImageIO;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * Classe com métodos utilitários.
 *
 * @author Manoel Afonso Filho
 */
public class Util {

    private static Logger log = LogManager.getLogger(Util.class.getName());
    /**
     * Gerador de números aleatórios criptograficamente seguros. Antes de utilizá-lo, se deve chamar o método
     * {@link Util#iniciarSecureRandom() iniciarSecureRandom}. Pode ser utilizado diretamente ou por meio dos métodos
     * utilitários disponíveis nesta classe.
     */
    public static SecureRandom sr;

    /**
     * Grava um arquivo em disco.
     *
     * @param arquivo Endereço do arquivo a ser salvo.
     * @param bytes Sequência binária do arquivo.
     */
    public static void escreverBytes(String arquivo, byte[] bytes) {
        try (FileOutputStream fos = new FileOutputStream(arquivo)) {
            fos.write(bytes);
        } catch (FileNotFoundException ex) {
            log.fatal("O arquivo \"{}\" não foi encontrado. Encerrando.", arquivo);
            System.exit(-1);
        } catch (IOException ioe) {
            log.fatal("Erro de escrita: {}", ioe);
            System.exit(-1);
        }
    }

    /**
     * Lê um arquivo do disco.
     *
     * @param arquivo Endereço do arquivo.
     * @return Conjunto de bytes do arquivo dado.
     */
    public static byte[] lerBytes(String arquivo) {
        InputStream inStream = null;
        BufferedInputStream bis = null;
        byte[] buf = null;
        try {
            inStream = new FileInputStream(arquivo);
            bis = new BufferedInputStream(inStream);

            int numByte = bis.available();
            buf = new byte[numByte];

            bis.read(buf);
        } catch (Exception ex) {
            log.fatal("Erro ao ler o arquivo. Encerrando. {}", ex);
        } finally {
            try {
                if (inStream != null) {
                    inStream.close();
                }
                if (bis != null) {
                    bis.close();
                }
            } catch (IOException ex) {
                log.fatal("Erro ao fechar os recursos de leitura. Encerrando. {}", ex);
            }
        }

        return buf;
    }

    /**
     * Divide uma sequência de bits em blocos de tamanho específico. Caso o último bloco não tenha bits o suficiente,
     * são inseridos bits de valor 0 para completar o último bloco.
     *
     * @param fonte Sequência binária.
     * @param tamanho Tamanho do bloco em <b>bytes</b>.
     * @return Blocos do arquivo.
     */
    public static byte[][] dividirArray(byte[] fonte, int tamanho) {
        byte[][] ret = new byte[(int) Math.ceil(fonte.length / (double) tamanho)][tamanho];

        int inicio = 0;

        for (int i = 0; i < ret.length; i++) {
            ret[i] = Arrays.copyOfRange(fonte, inicio, inicio + tamanho);
            inicio += tamanho;
        }

        return ret;
    }

    /**
     * Concatena blocos de bits em um único bloco.
     *
     * @param arrays Blocos de bits
     * @return Bloco resultante da concatenação dos blocos individuais.
     */
    public static byte[] concatenarArrays(byte[]... arrays) {
        //Determina o tamanho do array resultante.
        int tamTotal = 0;
        for (int i = 0; i < arrays.length; i++) {
            tamTotal += arrays[i].length;
        }

        //FIXME Não funciona com arquivos muito grandes.
        byte[] resultado = new byte[tamTotal];

        //Copia os arrays fonte no array resultado.
        int indiceAtual = 0;
        for (int i = 0; i < arrays.length; i++) {
            System.arraycopy(arrays[i], 0, resultado, indiceAtual, arrays[i].length);
            indiceAtual += arrays[i].length;
        }

        return resultado;
    }

    /**
     * Retorna uma string com os dados do array fornecido no formato
     * <code>
     * valor valor ... valor
     * </code>
     * @param array Array com os valores (não nulo).
     * @return Uma string com a representação do array.
     */
    public static String arrayToString(byte[] array){
        String t = "";
        for (int i = 0; i < array.length; i++) {
            t += array[i] + " ";
        }
        return t;
    }

    /**
     * Inicializa de forma segura o gerador de números aleatórios.
     */
    public static void iniciarSecureRandom(){
        try {
            sr = SecureRandom.getInstance("SHA1PRNG", "SUN");
        } catch (NoSuchAlgorithmException | NoSuchProviderException ex) {
            log.error("Erro ao obter instância do gerador de números aleatórios: {}", ex);
        }
        sr.nextBytes(new byte[]{});
    }

    /**
     * Gera um número pseudo-aleatório em um dado intervalo. A diferença entre o valor mínimo e máximo deve ser de no
     * máximo
     * <code>Integer.MAX_VALUE - 1</code>.
     *
     * @param max Valor máximo. Deve ser maior que o valor mínimo.
     * @return Inteirgo entre os min e max, exclusivo.
     */
    public static int randInt(int max) {
        int t = sr.nextInt() % max;
        if (t < 0) {
            t = -t;
        }

        return t;
    }

    public static class Imagem{
        public static BufferedImage bufferedImage;
        public static byte[] px;
    }

    public static void lerImagem(String arquivo){
        // open image
        File imgPath = new File(arquivo);
        BufferedImage bufferedImage;
        try {
            bufferedImage = ImageIO.read(imgPath);
            // get DataBufferBytes from Raster
            WritableRaster raster = bufferedImage.getRaster();
            DataBufferByte data = (DataBufferByte) raster.getDataBuffer();
            byte[] px = data.getData();
            Imagem img = new Imagem();
            Imagem.bufferedImage = bufferedImage;
            Imagem.px = px;
        } catch (IOException ex) {
            java.util.logging.Logger.getLogger(Util.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public static void saveImage(BufferedImage img, String ref) {
        try {
            String format = (ref.endsWith(".png")) ? "png" : "jpg";
            ImageIO.write(img, format, new File(ref));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
